<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"/>
<title>多层立库仿真</title>
<style>
  body{margin:0;overflow:hidden;font-family:Arial}
  #info{position:absolute;top:10px;left:10px;color:#fff;background:rgba(0,0,0,.6);padding:8px 12px;border-radius:4px}
</style>
</head>
<body>
<div id="info">
  <button id="btnPlay">暂停</button>
  <button id="btnSpeed">x1</button>
  <span id="stats">等待任务…</span>
</div>

  <script src="ThreeJs/js/three.js"></script>
  <script src="ThreeJs/js/OrbitControls.js"></script>
<script type="module">


// 1. 场景初始化 ----------------------------------------------------
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x202020);
const camera = new THREE.PerspectiveCamera(60, innerWidth/innerHeight, .1, 1000);
camera.position.set(0, 15, 20);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);

const ctrl = new THREE.OrbitControls(camera, renderer.domElement);
ctrl.enableDamping = true;

const amb = new THREE.AmbientLight(0xffffff, .6);
scene.add(amb);
const dir = new THREE.DirectionalLight(0xffffff, .8);
dir.position.set(5,15,5); dir.castShadow = true;
scene.add(dir);

const ground = new THREE.Mesh(
  new THREE.PlaneGeometry(30,30),
  new THREE.MeshStandardMaterial({ color:0x333 })
);
ground.rotation.x = -Math.PI/2; ground.receiveShadow = true;
scene.add(ground);

// 2. 立库参数 ------------------------------------------------------
const RACKS = 3, LEVELS = 3, CELLS_PER_SIDE = 3;
const RACK_W = 2, RACK_D = 4, CELL_H = 1.2, LIFT_W = 1.2;
const MAIN_Y = 0.5;

// 2.1 创建货架 ----------------------------------------------------
const rackGroup = new THREE.Group();
scene.add(rackGroup);
for (let r=0;r<RACKS;r++){
  for (let lvl=0;lvl<LEVELS;lvl++){
    for (let side of [-1,1]){ // -1=L, 1=R
      for (let c=0;c<CELLS_PER_SIDE;c++){
        const cell = new THREE.Mesh(
          new THREE.BoxGeometry(RACK_W-0.2, CELL_H-0.1, RACK_D-0.2),
          new THREE.MeshStandardMaterial({ color: 0x444, transparent:true, opacity:.15 })
        );
        cell.position.set(
          (r - (RACKS-1)/2) * (RACK_W + 1) + side*(RACK_W/2 + 0.1),
          MAIN_Y + (lvl+0.5)*CELL_H,
          5 + c * (RACK_D+0.5) * (side===-1?1:-1)
        );
        rackGroup.add(cell);
      }
    }
  }
}

// 2.2 水平主线 ----------------------------------------------------
const mainBelt = new THREE.Mesh(
  new THREE.BoxGeometry(20, 0.1, 1),
  new THREE.MeshStandardMaterial({ color:0x555 })
);
mainBelt.position.y = MAIN_Y;
mainBelt.receiveShadow = true;
scene.add(mainBelt);

// 2.3 横向接驳机 --------------------------------------------------
const transVersals = [];
for (let r=0;r<RACKS;r++){
  const tv = new THREE.Mesh(
    new THREE.BoxGeometry(1.2, 0.1, 3),
    new THREE.MeshStandardMaterial({ color:0x777 })
  );
  tv.position.set(
    (r - (RACKS-1)/2) * (RACK_W + 1),
    MAIN_Y,
    2.5
  );
  scene.add(tv);
  transVersals.push(tv);
}

// 2.4 提升机 ------------------------------------------------------
const lifts = [];
for (let r=0;r<RACKS;r++){
  const cage = new THREE.Mesh(
    new THREE.BoxGeometry(LIFT_W, CELL_H*LEVELS+0.2, LIFT_W),
    new THREE.MeshStandardMaterial({ color:0x00aaff })
  );
  cage.position.set(
    (r - (RACKS-1)/2) * (RACK_W + 1),
    MAIN_Y + (CELL_H*LEVELS)/2,
    1
  );
  scene.add(cage);
  lifts.push({ cage, y: MAIN_Y, targetY: null, occupied: false });
}

// 3. 货物类 --------------------------------------------------------
class Parcel {
  constructor(id, {rack, level, side}){
    this.id = id;
    this.target = {rack, level, side};
    const mat = new THREE.MeshStandardMaterial({ color: [0xff0000,0x00ff00,0x0000ff][rack] });
    this.mesh = new THREE.Mesh(new THREE.BoxGeometry(.6,.6,.6), mat);
    this.mesh.castShadow = true;
    this.mesh.position.set(-10, MAIN_Y+0.4, 0);
    scene.add(this.mesh);

    this.state = 'main';     // main → tv → lift → shelf
    this.lift = null;
    this.tvDir = 1;          // 1 向前, -1 向后
  }
  update(delta, speed){
    switch(this.state){
      case 'main':
        this.mesh.position.x += 4*delta*speed;
        const rx = (this.target.rack - 1)*(RACK_W+1);
        if (this.mesh.position.x >= rx - 0.5){
          this.state = 'tv';
          this.mesh.position.z = 0;
          this.tvDir = 1;
        }
        break;
      case 'tv':
        this.mesh.position.z += 2*delta*speed * this.tvDir;
        if (this.mesh.position.z >= 1){
          this.state = 'lift';
          this.lift = lifts[this.target.rack];
          this.lift.targetY = MAIN_Y + (this.target.level+0.5)*CELL_H;
          this.lift.occupied = this;
          this.mesh.position.z = 1;
        }
        break;
      case 'lift':
        if (this.lift.targetY && Math.abs(this.lift.cage.position.y - this.lift.targetY) > 0.05){
          const dy = (this.lift.targetY - this.lift.cage.position.y) * delta * 2 * speed;
          this.lift.cage.position.y += dy;
          this.mesh.position.y += dy;
        } else {
          this.state = 'shelf';
          const side = this.target.side==='L'?-1:1;
          this.mesh.position.set(
            this.lift.cage.position.x + side*(RACK_W/2+0.2),
            this.lift.targetY,
            5 + (Math.random()-0.5)
          );
          this.lift.occupied = null;
          this.lift.targetY = null;
          return true; // 完成
        }
        break;
    }
    return false;
  }
}

// 4. 主循环 --------------------------------------------------------
const parcels = new Map();
let paused = false, speed = 1;
const clock = new THREE.Clock();

function animate(){
  requestAnimationFrame(animate);
  if (!paused){
    const delta = clock.getDelta();
    for (let [id,p] of parcels){
      const done = p.update(delta, speed);
      if (done) parcels.delete(id);
    }
    document.getElementById('stats').textContent = `在线货物: ${parcels.size}`;
  }
  ctrl.update();
  renderer.render(scene, camera);
}
animate();

// 5. WebSocket 接收任务 -------------------------------------------
//const ws = new WebSocket(`ws://${location.host}`);
//ws.onmessage = e => {
//  const { id, target } = JSON.parse(e.data);
//  parcels.set(id, new Parcel(id, target));
//};
//ws.onopen = () => console.log('WS open');
//ws.onclose = () => console.warn('WS closed');

// 6. 交互按钮 ------------------------------------------------------
document.getElementById('btnPlay').onclick = ()=>{
  paused = !paused;
  document.getElementById('btnPlay').textContent = paused?'播放':'暂停';
};
document.getElementById('btnSpeed').onclick = ()=>{
  speed = speed===1?2:speed===2?.5:1;
  document.getElementById('btnSpeed').textContent = `x${speed}`;
};

// 7. 无后端时自动生成任务
let testId = 0;
setInterval(()=>{
  if (!paused && parcels.size < 6){
    const t = {
      rack: Math.floor(Math.random()*RACKS),
      level: Math.floor(Math.random()*LEVELS),
      side: Math.random()>.5?'L':'R'
    };
    //ws.onmessage({data:JSON.stringify({id:testId++, target:t})});
	const { id, target } = {id:testId++, target:t};
	parcels.set(id, new Parcel(id, target));
  }
}, 1000);

addEventListener('resize', ()=>{
  camera.aspect = innerWidth/innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});
</script>
</body>
</html>